import _, { keys } from 'lodash'
import knex from '../connection'
import type ITable from '../define/table'
import column from './column'
import type { AsyncVoid, AsyncDataResult, PromiseBoolean, Dictionary, } from '../define/types'
import base from './base'
import transaction from './transaction'

class Table implements ITable {
  async fixed(name: string, cols: Dictionary<string>, renames?: Dictionary<string>): AsyncVoid {
    const exists = await this.exists(name);

    if (!exists) {
      await this.create(name, cols);
    } else {
      const columns = await this.columns(name);
      const shouldRename = (col: string) => renames && renames.hasOwnProperty(col);
      const renamedValues = renames ? Object.values(renames) : [];
  
      for (const colName in cols) {
        if (!shouldRename(colName) && !renamedValues.includes(colName)) {
          const colExists = await column.exists(name, colName);
          if (!colExists) {
            await column.add(name, colName, cols[colName]);
          }
        }
      }
  
      for (const existingCol of columns) {
        const colField = existingCol.field;
        if (!cols.hasOwnProperty(colField) && !shouldRename(colField)) {
          await column.drop(name, colField);
        }
      }
    }

    if (renames) {
      for (const oldName in renames) {
        const newName = renames[oldName];
        const colExists = await column.exists(name, oldName);
        if (colExists) {
          await column.rename(name, oldName, newName);
        }
      }
    }
  }
  
  async create(name: string, cols: Dictionary<string>): AsyncVoid {
    return transaction.runInTransaction(async (trx) => {
      const id = base.showPrimaryKey() as string
      await trx.schema.createTable(name, (table: any) => {
        table.string(id).primary()
        _.forEach(cols, (colType, colName) => {
          if (colName && colType) {
            table.specificType(colName, colType).nullable()
          }
        })
        table.specificType('used', 'INTEGER').defaultTo(0)
        table.specificType('createdAt', 'TEXT DEFAULT CURRENT_TIMESTAMP')
        table.specificType('updatedAt', 'TEXT DEFAULT CURRENT_TIMESTAMP')
      })
    })
  }

  async drop(name: string): AsyncVoid {
    return transaction.runInTransaction(async (trx) => {
      await trx.schema.dropTableIfExists(name)
    })
  }

  async exists(name: string): PromiseBoolean {
    return await knex.schema.hasTable(name)
  }

  async truncate(name: string): AsyncVoid {
    return transaction.runInTransaction(async (trx) => {
      await trx(name).truncate()
    })
  }

  async rename(from: string, to: string): AsyncVoid {
    return transaction.runInTransaction(async (trx) => {
      await trx.schema.renameTable(from, to)
    })
  }

  async columns(name: string): AsyncDataResult {
    const cols: any[] = []
    const columns = await knex(name).columnInfo()
    _.keys(columns).forEach((key) => {
      cols.push({
        field: key,
        type: columns[key].type.toUpperCase()
      })
    })
    return cols
  }
}

export default new Table()
